using System;
using System.Collections;
using System.IO.Ports;
using System.Threading;
using Core;
using Core.Hardware.Watchdog;
using Microsoft.SPOT.Hardware;
using SecretLabs.NETMF.Hardware.Netduino;
using SolarDevices.Configuration;
using Watchdog = Core.Hardware.Watchdog.Watchdog;

namespace SolarDevices
{
    public class SerialDevice : IDisposable
    {
        private const int POLL_PERIOD = 6000;
        private const int CountDownToDisconnect = 6;
        private Timer _timer;
        private static SerialDevice _singleton;
        private SerialPort _netduinoSerialPort;
        private readonly ArrayList _data = new ArrayList();
        private readonly char[] _record = new char[100];
        private int _index;
        private bool _connected;
        private int _lastUpdate = CountDownToDisconnect;
        private readonly Watchdog _watchdog = null;
        private bool _startCode = false;

        static OutputPort _dtr = new OutputPort(Pins.GPIO_PIN_D5, false);
        static OutputPort _rts = new OutputPort(Pins.GPIO_PIN_D4, true);
        static InputPort _cts = new InputPort(Pins.GPIO_PIN_D2, true, Port.ResistorMode.PullUp);
        static InputPort _dsr = new InputPort(Pins.GPIO_PIN_D3, true, Port.ResistorMode.PullUp);

        public SerialDevice()
        {
            var sConfig = new SolarConfiguration();
            _watchdog = Watchdog.GetSingleton();
            _watchdog.RegisterTarget(Device.Serial);
        }

        public void Dispose()
        {
            StopPolling();
            _singleton = null;
        }

        public static SerialDevice GetSingleton()
        {
            return _singleton ?? (_singleton = new SerialDevice());
        }

        public void StartPolling()
        {
            if (_timer == null)
                SetupTimer();
        }

        private void SetupTimer()
        {
            _timer = new Timer(PollDevice, null, 0, POLL_PERIOD);
        }

        private void PollDevice(object state)
        {
            if (_netduinoSerialPort == null)
            {
                #if DEBUG
                _netduinoSerialPort = new SerialPort("COM2", 19200, Parity.None, 8, StopBits.One);
                #else
                _netduinoSerialPort = new SerialPort("COM1", 19200, Parity.None, 8, StopBits.One);
                #endif
                _netduinoSerialPort.Open();
                _netduinoSerialPort.DataReceived += SerialPortDataReceived;
                DebugLogger.GetLogger().Log("Connected to serial device");
                _connected = true;
            }
            else if (!_netduinoSerialPort.IsOpen)
            {
                _netduinoSerialPort.Open();
                _connected = true;
            }
            else
            {
                lock(this)
                {
                    if (--_lastUpdate <= 0)
                    {
                        Disconnect();
                        DebugLogger.TryLog("Lost connection to serial device");
                    }
                }
            }
            if (_watchdog != null && _lastUpdate != 0)
                _watchdog.CallIn(Device.Serial);
        }

        private void SerialPortDataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            try
            {
                DoRead();
            }
            catch (Exception ex)
            {
                Disconnect();
                DebugLogger.TryLog(ex);
            }
        }

        public void StopPolling()
        {
            if (_timer != null)
            {
                _timer.Dispose();
                _timer = null;
            }
            Disconnect();
        }

        private void Disconnect()
        {
            if (_netduinoSerialPort != null)
            {
                _netduinoSerialPort.Close();
                _netduinoSerialPort.DiscardOutBuffer();
                _netduinoSerialPort.DiscardInBuffer();
                _netduinoSerialPort.Dispose();
                _netduinoSerialPort = null;
            }
            _data.Clear();
            _connected = false;
        }

        private void DoRead()
        {
            //Determine how many bytes to read.
            var bytesToRead = _netduinoSerialPort.BytesToRead;
            if (bytesToRead > 0)
            {
                var buffer = new byte[bytesToRead];
                _netduinoSerialPort.Read(buffer, 0, bytesToRead);
                foreach (char ch in buffer)
                {
                    if (ch == 0x0a)
                    {
                        _index = 0;
                        _record[_index++] = ch;
                        _startCode = true;
                        _lastUpdate = CountDownToDisconnect;

                    }
                    else if (ch == 0x0d)
                    {
                        if (_startCode == false)
                        {
                            _index = 0;
                        }
                        else
                        {
                            _startCode = false;
                            _record[_index] = ch;
                            _index = 0;
                            var address = _record[1];
                            lock (this)
                            {
                                var foundRec = false;
                                foreach (char[] rec in _data)
                                {
                                    if (rec[1] == address)
                                    {
                                        _record.CopyTo(rec, 0);
                                        foundRec = true;
                                        break;
                                    }
                                }
                                if (!foundRec)
                                {
                                    _data.Add(_record);
                                }
                            }
                        }
                    }
                    else if (_startCode)
                    {
                        _record[_index++] = ch;
                    }
                    if (_index >= _record.Length)
                    {
                        _index = 0;
                        _startCode = false;
                    }
                }
            }
        }

        public enum OutbackDevice
        {
            FX,
            MX
        }

        public ArrayList GetData(OutbackDevice device)
        {
            var rVal = new ArrayList();
            lock (this)
            {
                foreach (char[] rec in _data)
                {
                    if (device == OutbackDevice.FX ? rec[1] < 0x3b : rec[1] > 0x40)
                    {
                        rVal.Add(rec);
                    }
                }
            }
            return rVal;
        }

        public bool Connected
        {
            get
            {
                bool rval;
                lock (this)
                {
                    rval = _connected;
                }
                return rval;
            }
        }
    }
}
